package mbsl.main;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import mbsl.system.Core;
import mbsl.system.TextProvider;
import mbsl.util.SWTUtils;
import static mbsl.util.SWTUtils.Direction.*;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;

/**
 * The Main Frame
 * <p>
 * TabItems registered on this frame's TabFolder will receive a couple of custom events:<br>
 * If the tab loses focus, an SWT.FocusOut event will be sent to the item.<br>
 * If the tab gains focus, an SWT.FocusIn event will be sent to the item.
 * <p>
 * These events can be captured with the following code:
 * <pre>
 * tabItem.addListener(SWT.FocusIn, new Listener() {
 * 	public void handleEvent(Event pEvent) {
 * 		// TODO: Handle event
 * 	}
 * });
 * tabItem.addListener(SWT.FocusOut, new Listener() {
 * 	public void handleEvent(Event pEvent) {
 * 		// TODO: Handle event
 * 	}
 * });
 * </pre>
 * Note that neither of these will be issued on first load.  To assess whether a component has focus after it has been loaded,
 * use
 * <pre>
 * mainFrame.getTabFolder().getSelection()[0] == pItem;
 * </pre>
 * @see MainController#hasFocus(TabItem)
 */
class MainFrame {
	private Display mDisplay;
	private Shell mWindow;
	private MainStatusPanel mStatusPanel;
	private TabFolder mTabFolder;
	private TextProvider tp = Core.tp;
	
	MainFrame(Display pDisplay) {
		mDisplay = pDisplay;
		
		mWindow = new Shell(mDisplay);
		mWindow.setText(tp.get("main.MainFrame.title"));
		mWindow.setMenuBar(new Menu(mWindow, SWT.BAR));
		mWindow.setLayout(new FormLayout());
		
		mTabFolder = new TabFolder(mWindow, SWT.TOP);
		
		mTabFolder.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(down, 100, -20),
				SWTUtils.getMeta(right, 100, 0),
				SWTUtils.getMeta(left, 0, 2),
				SWTUtils.getMeta(up, 0, 0)
		));
		mTabFolder.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent pE) {
				TabItem newActive = mTabFolder.getSelection()[0];

				if (newActive == mActive)
					return;
				if (mActive != null)
					mActive.notifyListeners(SWT.FocusOut, new Event());

				newActive.notifyListeners(SWT.FocusIn, new Event());
				
				mActive = newActive;
			}
		});
		
		mStatusPanel = new MainStatusPanel(mWindow, SWTUtils.getFormData(
				SWTUtils.getMeta(down, 100, 0),
				SWTUtils.getMeta(right, 100, 0),
				SWTUtils.getMeta(left, 0, 0),
				SWTUtils.getMeta(up, 100, -20)
		));
		
		mWindow.pack();
	}
	
	void close() {
		mWindow.dispose();
	}
	
	interface StatusPanel {
		/**
		 * Sets the progress bar to the provided percentage.
		 * <p>
		 * If the percentage is set to 100, it will delay for one second, then clear the bar.
		 * Subsequent setProgress(100) calls will further delay the clearing.  Calls to this
		 * method with other values will stop the timer.  Values outside [0, 100] will be
		 * set to the closest valid value.
		 * @param pPercentage
		 */
		void setProgress(int percentage);
		/**
		 * Sets the text of the info section of the status panel.  Use clearInfo() to clear.
		 */
		void setInfo(String info);
		/**
		 * Sets the text of the status section of the status panel.
		 */
		void setStatus(String status);
		/**
		 * Clears the text of the info section of the status panel.  The same as setInfo(null).
		 */
		void clearInfo();
	}
	class MainStatusPanel implements StatusPanel {
		private Composite mPanel;
		private ProgressBar mProgress;
		private ExecutorService mThreadPool;
		private DelayerThread mDelayer;
		private Label mInfo;
		private Label mStatus;
		
		MainStatusPanel(Shell pWindow, FormData pFormData) {
			mPanel = new Composite(pWindow, SWT.TRANSPARENT);
			mThreadPool = Executors.newFixedThreadPool(1);

			mStatus = new Label(mPanel, SWT.LEFT | SWT.TRANSPARENT);
			Label vertical1 = SWTUtils.getVerticalBar(mPanel);
			mInfo = new Label(mPanel, SWT.LEFT | SWT.TRANSPARENT);
			Label vertical2 = SWTUtils.getVerticalBar(mPanel);
			mProgress = new ProgressBar(mPanel, SWT.HORIZONTAL | SWT.TRANSPARENT);

			mPanel.setLayoutData(pFormData);
			mPanel.setLayout(new FormLayout());
			mPanel.addDisposeListener(new DisposeListener() {
				@Override
				public void widgetDisposed(DisposeEvent pE) {
					mThreadPool.shutdown();
				}
			});

			mProgress.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(down, 100, -2),
					SWTUtils.getMeta(right, 100, -3),
					SWTUtils.getMeta(left, 70, 2),
					SWTUtils.getMeta(up, 0, 2)
			));

			mStatus.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(left, 0, 2),
					SWTUtils.getMeta(up, 0, 2),
					SWTUtils.getMeta(down, 100, 0),
					SWTUtils.getMeta(right, 20, -2)
			));
			
			mInfo.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(left, vertical1, 2),
					SWTUtils.getMeta(right, vertical2, -3),
					SWTUtils.getMeta(up, 0, 2),
					SWTUtils.getMeta(down, 100, 0)
			));

			vertical1.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, 3),
					SWTUtils.getMeta(down, 100, -3),
					SWTUtils.getMeta(left, mStatus, 3)
			));

			vertical2.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, 3),
					SWTUtils.getMeta(down, 100, -3),
					SWTUtils.getMeta(right, mProgress, -2)
			));
			
			mPanel.pack();
		}
		public void setProgress(int pPercentage) {
			if (pPercentage < 0)
				pPercentage = 0;
			else if (pPercentage > 100)
				pPercentage = 100;
			
			final int percentage = pPercentage;
			
			SWTUtils.exec(mDisplay, new Runnable() {
				public void run() {
					mProgress.setSelection(percentage);
				}
			});
				
			if (pPercentage != 100) {
				if (mDelayer != null)
					mDelayer.interrupt();
					
				mDelayer = null;
			} else {
				mDelayer = new DelayerThread();

				mThreadPool.execute(mDelayer);
			}
		}
		public void setInfo(String pInfoMessage) {
			mInfo.setText(pInfoMessage != null ? pInfoMessage : "");
		}
		public void clearInfo() {
			setInfo(null);
		}
		private class DelayerThread extends Thread {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);

					setProgress(0);
				} catch (InterruptedException e) {}
			}
		}
		public void setStatus(String pStatus) {
			mStatus.setText(pStatus != null ? pStatus : "");
		}
		Composite getPanel() {
			return mPanel;
		}
	}
	TabFolder getTabFolder() {
		return mTabFolder;
	}
	void open() {
		mWindow.open();
	}
	StatusPanel getStatusPanel() {
		return mStatusPanel;
	}
	Shell getShell() {
		return mWindow;
	}
	/**
	 * @see MenuBarController#remove(String)
	 */
	void removeMenuItem(String pMenuPath) {
		String[] paths = pMenuPath.split("\\s*//\\s*");
		Deque<MenuItem> stack = new ArrayDeque<MenuItem>(paths.length);
		Menu parent = mWindow.getMenuBar();
		
		for (int i = 0; i < paths.length; i++) {
			for (MenuItem menuItem : parent.getItems()) {
				String searchItemName = paths[i].replaceAll("&", "");
				String menuItemName = menuItem.getText().replaceAll("&", "");
			
				if (menuItemName.equals(searchItemName)) {
					stack.push(menuItem);
					break;
				}
			}

			if (stack.size() != i + 1)
				throw new MenuItemFormatException(String.format(tp.get("error.noexist"), pMenuPath));

			if (i < paths.length - 1) {
				parent = stack.peek().getMenu();
			} else {
				MenuItem lastOrphan = stack.pop();
				
				while (!stack.isEmpty() && stack.peek().getMenu().getItemCount() == 1)
					lastOrphan = stack.pop();
				
				lastOrphan.dispose();
			}
		}
	}
	/**
	 * @see MenuBarController#add(String, int, int)
	 */
	MenuItem getMenuItem(String pMenuPath, int pType, int pIndex) {
		// Split the path up
		String[] paths = pMenuPath.split("\\s*//\\s*");
		
		// Set the menu bar as parent
		Menu parent = mWindow.getMenuBar();
		
		// For each token in the path
		for (int i = 0; i < paths.length; i++) {
			MenuItem item = null;
			// Remove the mnemonics from the token
			String searchItemName = paths[i].replaceAll("&", "");
			// For each menu item with this parent
			for (MenuItem menuItem : parent.getItems()) {
				// Remove the mnemonics from the name
				String menuItemName = menuItem.getText().replaceAll("&", "");
				
				// If the name fits
				if (menuItemName.equals(searchItemName)) {
					// keep this item and stop the loop
					item = menuItem;
					break;
				}
			}
			
			// If there is more than one token to go
			if (i < paths.length - 1) {
				// If the item wasn't found
				if (item == null) {
					// add it
					item = new MenuItem(parent, SWT.CASCADE);

					item.setText(paths[i]);
					item.setMenu(new Menu(mWindow, SWT.DROP_DOWN));
				}
				// Set the found or newly created item as parent, and go to the next token
				parent = item.getMenu();
			// If we're at the last token
			} else {
				// If the item wasn't found
				if (item == null) {
					// If the index argument was set
					if (pIndex > -1 )
						// Add a new item at that index
						item = new MenuItem(parent, pType, pIndex);
					// If it wasn't
					else
						// Add a new item at the end
						item = new MenuItem(parent, pType);
					
					item.setText(paths[i]);
				}
				// Return the token that was found or newly created
				return item;
			}
		}
		
		// If we end up here, something has gone terribly wrong.
		throw new MenuItemFormatException("Failed to add menu item, because the loop ended errorously.");
	}
	@SuppressWarnings("serial")
	class MenuItemFormatException extends RuntimeException {
		public MenuItemFormatException(String pMessage) {
			super(pMessage);
		}
	}
	
	private TabItem mActive;
}